In Part 1, we showed how to preprocess scRNA-seq isoform datasets and create a multi Assay Seurat object. In this part, we’ll use this Seurat object to perform differential isoform expression analysis and explore pertinent biological questions. We’ll focus on searching for genes with a transcript isoform expression that differed between clusters, which we refer to as “isoform switches”.

We start the analysis by loading the necessary packages:

library(dplyr)
library(patchwork)
library(ggplot2)
library(reactable)

Plotting functions

see_isoforms_expression = function(sobj, gene, limits) {
  isoforms = grep(paste0("^", gene, "\\."), rownames(sobj), value = TRUE)
  
  plot_list = lapply(isoforms, FUN = function(one_isoform) {
    p = Seurat::FeaturePlot(sobj, features = one_isoform,
                            reduction = name2D, order = TRUE) +
      ggplot2::labs(title = one_isoform) +
      ggplot2::scale_color_gradientn(limits = limits, colors = c("lightgray", "#FDBB84", "#EF6548", "#7F0000", "black")) +
      ggplot2::theme(aspect.ratio = 1,
                     plot.subtitle = element_text(hjust = 0.5)) +
      Seurat::NoAxes()
    return(p)
  })
  
  return(plot_list)
}

see_genes_expression <- function(gene){
  Seurat::FeaturePlot(seurat_obj, gene, reduction = name2D)+
      ggplot2::scale_color_gradientn(limits = c(0,4), colors = c("lightgray", "#FDBB84", "#EF6548", "#7F0000", "black")) +
      ggplot2::theme(aspect.ratio = 1,
                     plot.subtitle = element_text(hjust = 0.5)) +
      Seurat::NoAxes()
}

We set the global settings of the analysis. We will store data there :

out_dir = "./output/"
data_dir <- "./data/"

2.1 Loading the Seurat object

We load the Seurat object:

seurat_obj = readRDS(paste0(out_dir, "/datasets/", "seurat_obj_annotated.rds"))
color_markers = readRDS(paste0(out_dir, "/datasets/", "color_markers.rds"))

This is the projection of interest:

name2D =  "RNA_pca_10_tsne"

2.2 Cell visualization

We visualize cells:

cell_type = Seurat::DimPlot(seurat_obj, reduction = name2D, group.by = "cluster_cell_type") +
  Seurat::NoAxes() +
  ggplot2::scale_color_manual(values = unlist(color_markers),
                              breaks = names(color_markers)) +
  ggplot2::theme(aspect.ratio = 1,
                 plot.title = element_text(hjust = 0.5),
                 plot.subtitle = element_text(hjust = 0.5))

clusters = Seurat::DimPlot(seurat_obj, group.by = "seurat_clusters", label = TRUE,
                           reduction = name2D) +
  Seurat::NoAxes() + ggplot2::ggtitle("Clustering") +
  ggplot2::theme(aspect.ratio = 1,
                 plot.title = element_text(hjust = 0.5))

clusters | cell_type

2.3 Differential isoforms expression (DIE)

In this section, we’ll use the ISO_SWITCH_ALL function from Isoswitch to analyze isoform switches between cell types. we refer to “isoform switches.” when two isoforms of the same gene are markers of different cell clusters. The ISO_SWITCH_ALL function uses Seurat’s FindMarkers function to identify marker isoforms for each cluster in a gene. It outputs a data frame, listing each transcript marker in a separate row.

clusters = levels(seurat_obj@active.ident)
switch_markers = isoswitch::ISO_SWITCH_ALL(seurat_obj, clusters, assay = "ISO", 
                                           min.pct = 0, logfc.threshold = 0.40)

2.3.1 Save DIE list

We save the list of DE isoforms :

saveRDS(switch_markers, file = paste0(out_dir, "./datasets/", "switch_markers_combined_all.rds"))

2.4 DIE visualisation

We load the DE isoforms table :

switch_markers = readRDS(paste0(out_dir, "/datasets/switch_markers_combined_all.rds"))

2.4.1 Explore the switch markers table

In this section, we explore the switch_marker dataframe.

List of isoform switchs:

isoswitch::gene_switch_table(switch_markers)

Can you identify the genes and cell types that show the most significant changes in isoform expression?

To facilitate the graphical interpretation of the results:

  • plot_marker_matrix() produces a heatmap of number of unique genes per contrast between clusters
  • plot_marker_score() produces a volcano plot showing p-values and average logFC for each gene with an isoform switch

We visualize the number of DE isoforms between each cluster :

pl1 = isoswitch::plot_marker_matrix(seurat_obj, switch_markers)
pl2 = isoswitch::plot_marker_score(seurat_obj, switch_markers)
pl1 | pl2

Individual volcano plots for each cluster.

isoswitch::plot_marker_score(seurat_obj, switch_markers, facet=TRUE, ncol=3)

2.4.2 Visualization of specific genes

In this section, we visualize isoforms expression level for specific genes.

First, we set the assay to “ISO” because all expression levels are stored there.

Seurat::DefaultAssay(seurat_obj) = "ISO"

Let’s take the example of Clathrin light chain A (Clta) gene isoform expression switch during neuronal maturation:

see_isoforms_expression(seurat_obj, "Clta", c(0,4)) %>%
  patchwork::wrap_plots(., ncol = 3)

Seurat::FeaturePlot(seurat_obj, reduction = name2D, features = c("Clta..ENSMUST00000107849", "Clta..ENSMUST00000170241"), blend = TRUE, order = TRUE)

2.4.3 Report

In this section, we generate a small report.

We load annotation files: The Isoswitch documentation provides a comprehensive guide on how to build your annotation metadata.

gtf_df <- readRDS(paste0(data_dir,"annotation/gtf_rds.rds"))
gene_metadata <- readRDS(paste0(data_dir,"annotation/gene_metadata.rds"))
transcript_metadata <- readRDS(paste0(data_dir,"annotation/transcript_metadata.rds"))

For Clta gene:

gene_switches <- isoswitch::compute_switches(switch_markers, gene="Clta")
knitr::kable(isoswitch::format_switch_table(gene_switches))
geneId t1 c1 p1 log2fc1 t2 c2 p2 log2fc2
Clta ENSMUST00000107849 Glutamatergic 2.69e-23 5.35 ENSMUST00000170241 radial_glia 4.11e-20 -2.29
Clta ENSMUST00000107849 Glutamatergic 4.75e-27 2.35 ENSMUST00000170241 cyclin_radial_glia 7.11e-20 -2.03
Clta ENSMUST00000107849 imature_Glutamatergic 1.39e-20 5.03 ENSMUST00000170241 radial_glia 3.87e-12 -1.55
Clta ENSMUST00000107849 GABAergic 1.09e-18 5.54 ENSMUST00000170241 radial_glia 1.56e-11 -1.73
Clta ENSMUST00000107849 GABAergic 2.35e-19 2.54 ENSMUST00000170241 cyclin_radial_glia 1.34e-10 -1.46
Clta ENSMUST00000107849 imature_Glutamatergic 9.33e-20 2.03 ENSMUST00000170241 cyclin_radial_glia 1.54e-10 -1.28
Clta ENSMUST00000170241 intermediate_progenitors 3.60e-10 -2.11 ENSMUST00000107849 Glutamatergic 4.35e-09 1.21
Clta ENSMUST00000107849 GABAergic 3.37e-06 1.40 ENSMUST00000170241 intermediate_progenitors 1.55e-05 -1.54
Clta ENSMUST00000107849 GABAergic 1.09e-18 5.54 ENSMUST00000107845 radial_glia 8.57e-05 -2.16
Clta ENSMUST00000170241 intermediate_progenitors 1.50e-04 -1.36 ENSMUST00000107849 imature_Glutamatergic 8.12e-03 0.89
Clta ENSMUST00000107849 imature_Glutamatergic 1.39e-20 5.03 ENSMUST00000107845 radial_glia 1.57e-04 -1.84
Clta ENSMUST00000107849 Glutamatergic 2.69e-23 5.35 ENSMUST00000107845 radial_glia 4.01e-03 -1.94
Clta ENSMUST00000107849 imature_GABAergic 5.20e-07 4.49 ENSMUST00000170241 radial_glia 7.68e-03 -0.81
Clta ENSMUST00000107851 Cajal_Retzius 2.21e-02 6.09 ENSMUST00000170241 radial_glia 2.58e-02 -2.45
Clta ENSMUST00000107849 Cajal_Retzius 2.43e-04 4.38 ENSMUST00000170241 radial_glia 2.58e-02 -2.45
Clta ENSMUST00000107849 Glutamatergic 2.46e-09 -0.85 ENSMUST00000170241 imature_GABAergic 2.70e-02 1.49
Clta ENSMUST00000107849 imature_GABAergic 5.20e-07 4.49 ENSMUST00000107845 radial_glia 2.97e-02 -1.38
isoswitch::isoswitch_report(seurat_obj, "ISO", gene="Clta", marker_list=switch_markers, gtf_df, transcript_metadata) 
## [1] "Locus"
## [1] "Junction"
## [1] "umi counts"
## [1] "dotplot"
## [1] "PATCHWORK"

  • Now, try applying the same analysis to explore isoform expression for other genes of interest.

2.5 Save

We save the list of DE isoforms :

saveRDS(switch_markers, file = paste0(out_dir, "./datasets/", "switch_markers_combined_all.rds"))

R session

## R version 4.2.2 (2022-10-31)
## Platform: aarch64-apple-darwin20 (64-bit)
## Running under: macOS 14.6
## 
## Matrix products: default
## BLAS:   /Library/Frameworks/R.framework/Versions/4.2-arm64/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.2-arm64/Resources/lib/libRlapack.dylib
## 
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] reactable_0.4.4 ggplot2_3.4.0   patchwork_1.1.2 dplyr_1.1.4    
## 
## loaded via a namespace (and not attached):
##   [1] backports_1.4.1        spam_2.10-0            Hmisc_4.7-2           
##   [4] plyr_1.8.8             igraph_2.0.1.1         lazyeval_0.2.2        
##   [7] sp_1.5-1               splines_4.2.2          RcppHNSW_0.5.0        
##  [10] crosstalk_1.2.0        listenv_0.9.0          scattermore_1.2       
##  [13] digest_0.6.31          htmltools_0.5.4        fansi_1.0.3           
##  [16] checkmate_2.1.0        magrittr_2.0.3         tensor_1.5            
##  [19] cluster_2.1.4          ROCR_1.0-11            limma_3.54.2          
##  [22] globals_0.16.2         matrixStats_0.63.0     spatstat.sparse_3.0-0 
##  [25] jpeg_0.1-10            colorspace_2.0-3       ggrepel_0.9.2         
##  [28] xfun_0.36              jsonlite_1.8.4         progressr_0.12.0      
##  [31] spatstat.data_3.0-0    survival_3.4-0         zoo_1.8-11            
##  [34] glue_1.6.2             polyclip_1.10-4        gtable_0.3.1          
##  [37] leiden_0.4.3           ggtranscript_0.99.9    future.apply_1.10.0   
##  [40] isoswitch_0.0.0.9000   abind_1.4-5            scales_1.2.1          
##  [43] DBI_1.1.3              spatstat.random_3.0-1  miniUI_0.1.1.1        
##  [46] Rcpp_1.0.9             viridisLite_0.4.2      xtable_1.8-4          
##  [49] htmlTable_2.4.1        reticulate_1.34.0      foreign_0.8-84        
##  [52] dotCall64_1.1-0        Formula_1.2-4          htmlwidgets_1.6.0     
##  [55] httr_1.4.4             RColorBrewer_1.1-3     ellipsis_0.3.2        
##  [58] Seurat_5.0.0           ica_1.0-3              pkgconfig_2.0.3       
##  [61] farver_2.1.1           nnet_7.3-18            sass_0.4.4            
##  [64] uwot_0.1.14            deldir_1.0-6           utf8_1.2.2            
##  [67] tidyselect_1.2.0       labeling_0.4.2         rlang_1.1.3           
##  [70] reshape2_1.4.4         later_1.3.0            reactR_0.5.0          
##  [73] munsell_0.5.0          tools_4.2.2            cachem_1.0.6          
##  [76] cli_3.6.2              generics_0.1.3         ggridges_0.5.4        
##  [79] evaluate_0.19          stringr_1.5.0          fastmap_1.1.0         
##  [82] yaml_2.3.6             goftest_1.2-3          knitr_1.41            
##  [85] fitdistrplus_1.1-8     purrr_1.0.2            RANN_2.6.1            
##  [88] pbapply_1.6-0          future_1.30.0          nlme_3.1-161          
##  [91] mime_0.12              compiler_4.2.2         rstudioapi_0.14       
##  [94] plotly_4.10.1          png_0.1-8              spatstat.utils_3.0-4  
##  [97] tibble_3.2.1           bslib_0.4.2            stringi_1.7.8         
## [100] highr_0.10             RSpectra_0.16-1        forcats_0.5.2         
## [103] lattice_0.20-45        Matrix_1.6-2           vctrs_0.6.5           
## [106] pillar_1.9.0           lifecycle_1.0.3        spatstat.geom_3.0-3   
## [109] lmtest_0.9-40          jquerylib_0.1.4        RcppAnnoy_0.0.20      
## [112] data.table_1.14.6      cowplot_1.1.1          irlba_2.3.5.1         
## [115] httpuv_1.6.7           R6_2.5.1               latticeExtra_0.6-30   
## [118] promises_1.2.0.1       KernSmooth_2.23-20     gridExtra_2.3         
## [121] parallelly_1.33.0      codetools_0.2-18       assertthat_0.2.1      
## [124] fastDummies_1.7.3      MASS_7.3-58.1          withr_2.5.0           
## [127] SeuratObject_5.0.0     sctransform_0.4.1      parallel_4.2.2        
## [130] reactablefmtr_2.0.0    grid_4.2.2             rpart_4.1.19          
## [133] tidyr_1.3.1            rmarkdown_2.19         Rtsne_0.16            
## [136] spatstat.explore_3.0-5 shiny_1.7.4            base64enc_0.1-3       
## [139] interp_1.1-3
LS0tCnRpdGxlOiAic2NSTkFzZXEgbG9uZyByZWFkIGFuYWx5c2lzIC0gUGFydCAyIgphdXRob3I6ICJNb3JnYW5lIEZpZXJ2aWxsZSAmIEFsaSBIYW1yYW91aSIKZGF0ZTogIjIwMjQtMDktMDYiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgY29kZV9mb2xkaW5nOiBzaG93CiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgbnVtYmVyX3NlY3Rpb25zOiBmYWxzZQotLS0KCjxzdHlsZT4KYm9keSB7CnRleHQtYWxpZ246IGp1c3RpZnl9Cjwvc3R5bGU+Cgo8IS0tIFNldCBkZWZhdWx0IHBhcmFtZXRlcnMgZm9yIGFsbCBjaHVua3MgLS0+CmBgYHtyLCBzZXR1cCwgaW5jbHVkZSA9IEZBTFNFfQpzZXQuc2VlZCgxMzM3TCkKa25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLAoKICAgICAgICAgICAgICAgICAgICAgIG1lc3NhZ2UgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgIHdhcm5pbmcgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgIGZvbGRfb3V0cHV0ID0gRkFMU0UsIAogICAgICAgICAgICAgICAgICAgICAgZm9sZF9wbG90ID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgIGZpZy5hbGlnbiA9ICdjZW50ZXInLAogICAgICAgICAgICAgICAgICAgICAgZmlnLndpZHRoID0gMjAsCiAgICAgICAgICAgICAgICAgICAgICBmaWcuaGVpZ2h0ID0gMTUpCmBgYAoKSW4gUGFydCAxLCB3ZSBzaG93ZWQgaG93IHRvIHByZXByb2Nlc3Mgc2NSTkEtc2VxIGlzb2Zvcm0gZGF0YXNldHMgYW5kIGNyZWF0ZSBhIG11bHRpIEFzc2F5IFNldXJhdCBvYmplY3QuIEluIHRoaXMgcGFydCwgd2UnbGwgdXNlIHRoaXMgU2V1cmF0IG9iamVjdCB0byBwZXJmb3JtIGRpZmZlcmVudGlhbCBpc29mb3JtIGV4cHJlc3Npb24gYW5hbHlzaXMgYW5kIGV4cGxvcmUgcGVydGluZW50IGJpb2xvZ2ljYWwgcXVlc3Rpb25zLiBXZSdsbCBmb2N1cyBvbiBzZWFyY2hpbmcgZm9yIGdlbmVzIHdpdGggYSB0cmFuc2NyaXB0IGlzb2Zvcm0gZXhwcmVzc2lvbiB0aGF0IGRpZmZlcmVkIGJldHdlZW4gY2x1c3RlcnMsIHdoaWNoIHdlIHJlZmVyIHRvIGFzICJpc29mb3JtIHN3aXRjaGVzIi4KCldlIHN0YXJ0IHRoZSBhbmFseXNpcyBieSBsb2FkaW5nIHRoZSBuZWNlc3NhcnkgcGFja2FnZXM6CgpgYGB7ciBsaWJyYXJ5LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBwYWdlZC5wcmludD1GQUxTRSwgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CmxpYnJhcnkoZHBseXIpCmxpYnJhcnkocGF0Y2h3b3JrKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkocmVhY3RhYmxlKQpgYGAKClBsb3R0aW5nIGZ1bmN0aW9ucwoKYGBge3IgZnVuY3Rpb25zLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0Kc2VlX2lzb2Zvcm1zX2V4cHJlc3Npb24gPSBmdW5jdGlvbihzb2JqLCBnZW5lLCBsaW1pdHMpIHsKICBpc29mb3JtcyA9IGdyZXAocGFzdGUwKCJeIiwgZ2VuZSwgIlxcLiIpLCByb3duYW1lcyhzb2JqKSwgdmFsdWUgPSBUUlVFKQogIAogIHBsb3RfbGlzdCA9IGxhcHBseShpc29mb3JtcywgRlVOID0gZnVuY3Rpb24ob25lX2lzb2Zvcm0pIHsKICAgIHAgPSBTZXVyYXQ6OkZlYXR1cmVQbG90KHNvYmosIGZlYXR1cmVzID0gb25lX2lzb2Zvcm0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24gPSBuYW1lMkQsIG9yZGVyID0gVFJVRSkgKwogICAgICBnZ3Bsb3QyOjpsYWJzKHRpdGxlID0gb25lX2lzb2Zvcm0pICsKICAgICAgZ2dwbG90Mjo6c2NhbGVfY29sb3JfZ3JhZGllbnRuKGxpbWl0cyA9IGxpbWl0cywgY29sb3JzID0gYygibGlnaHRncmF5IiwgIiNGREJCODQiLCAiI0VGNjU0OCIsICIjN0YwMDAwIiwgImJsYWNrIikpICsKICAgICAgZ2dwbG90Mjo6dGhlbWUoYXNwZWN0LnJhdGlvID0gMSwKICAgICAgICAgICAgICAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpICsKICAgICAgU2V1cmF0OjpOb0F4ZXMoKQogICAgcmV0dXJuKHApCiAgfSkKICAKICByZXR1cm4ocGxvdF9saXN0KQp9CgpzZWVfZ2VuZXNfZXhwcmVzc2lvbiA8LSBmdW5jdGlvbihnZW5lKXsKICBTZXVyYXQ6OkZlYXR1cmVQbG90KHNldXJhdF9vYmosIGdlbmUsIHJlZHVjdGlvbiA9IG5hbWUyRCkrCiAgICAgIGdncGxvdDI6OnNjYWxlX2NvbG9yX2dyYWRpZW50bihsaW1pdHMgPSBjKDAsNCksIGNvbG9ycyA9IGMoImxpZ2h0Z3JheSIsICIjRkRCQjg0IiwgIiNFRjY1NDgiLCAiIzdGMDAwMCIsICJibGFjayIpKSArCiAgICAgIGdncGxvdDI6OnRoZW1lKGFzcGVjdC5yYXRpbyA9IDEsCiAgICAgICAgICAgICAgICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSArCiAgICAgIFNldXJhdDo6Tm9BeGVzKCkKfQpgYGAKCldlIHNldCB0aGUgZ2xvYmFsIHNldHRpbmdzIG9mIHRoZSBhbmFseXNpcy4gV2Ugd2lsbCBzdG9yZSBkYXRhIHRoZXJlIDoKCmBgYHtyIG91dF9kaXIsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpvdXRfZGlyID0gIi4vb3V0cHV0LyIKZGF0YV9kaXIgPC0gIi4vZGF0YS8iCmBgYAoKIyMjIDIuMSBMb2FkaW5nIHRoZSBTZXVyYXQgb2JqZWN0CgpXZSBsb2FkIHRoZSBTZXVyYXQgb2JqZWN0OgoKYGBge3IgbG9hZF9zb2JqfQpzZXVyYXRfb2JqID0gcmVhZFJEUyhwYXN0ZTAob3V0X2RpciwgIi9kYXRhc2V0cy8iLCAic2V1cmF0X29ial9hbm5vdGF0ZWQucmRzIikpCmNvbG9yX21hcmtlcnMgPSByZWFkUkRTKHBhc3RlMChvdXRfZGlyLCAiL2RhdGFzZXRzLyIsICJjb2xvcl9tYXJrZXJzLnJkcyIpKQpgYGAKClRoaXMgaXMgdGhlIHByb2plY3Rpb24gb2YgaW50ZXJlc3Q6CgpgYGB7ciBuYW1lMkR9Cm5hbWUyRCA9ICAiUk5BX3BjYV8xMF90c25lIgpgYGAKCiMjIyAyLjIgQ2VsbCB2aXN1YWxpemF0aW9uCgpXZSB2aXN1YWxpemUgY2VsbHM6CgpgYGB7ciBzZWVfY2VsbHMsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTEyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBwYWdlZC5wcmludD1GQUxTRX0KY2VsbF90eXBlID0gU2V1cmF0OjpEaW1QbG90KHNldXJhdF9vYmosIHJlZHVjdGlvbiA9IG5hbWUyRCwgZ3JvdXAuYnkgPSAiY2x1c3Rlcl9jZWxsX3R5cGUiKSArCiAgU2V1cmF0OjpOb0F4ZXMoKSArCiAgZ2dwbG90Mjo6c2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IHVubGlzdChjb2xvcl9tYXJrZXJzKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gbmFtZXMoY29sb3JfbWFya2VycykpICsKICBnZ3Bsb3QyOjp0aGVtZShhc3BlY3QucmF0aW8gPSAxLAogICAgICAgICAgICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLAogICAgICAgICAgICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQoKY2x1c3RlcnMgPSBTZXVyYXQ6OkRpbVBsb3Qoc2V1cmF0X29iaiwgZ3JvdXAuYnkgPSAic2V1cmF0X2NsdXN0ZXJzIiwgbGFiZWwgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24gPSBuYW1lMkQpICsKICBTZXVyYXQ6Ok5vQXhlcygpICsgZ2dwbG90Mjo6Z2d0aXRsZSgiQ2x1c3RlcmluZyIpICsKICBnZ3Bsb3QyOjp0aGVtZShhc3BlY3QucmF0aW8gPSAxLAogICAgICAgICAgICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQoKY2x1c3RlcnMgfCBjZWxsX3R5cGUKYGBgCgojIyMgMi4zIERpZmZlcmVudGlhbCBpc29mb3JtcyBleHByZXNzaW9uIChESUUpCgpJbiB0aGlzIHNlY3Rpb24sIHdlJ2xsIHVzZSB0aGUgYElTT19TV0lUQ0hfQUxMYCBmdW5jdGlvbiBmcm9tIElzb3N3aXRjaCB0byBhbmFseXplIGlzb2Zvcm0gc3dpdGNoZXMgYmV0d2VlbiBjZWxsIHR5cGVzLiB3ZSByZWZlciB0byAiaXNvZm9ybSBzd2l0Y2hlcy4iIHdoZW4gdHdvIGlzb2Zvcm1zIG9mIHRoZSBzYW1lIGdlbmUgYXJlIG1hcmtlcnMgb2YgZGlmZmVyZW50IGNlbGwgY2x1c3RlcnMuICBUaGUgYElTT19TV0lUQ0hfQUxMYCBmdW5jdGlvbiB1c2VzIFNldXJhdCdzIGBGaW5kTWFya2Vyc2AgZnVuY3Rpb24gdG8gaWRlbnRpZnkgbWFya2VyIGlzb2Zvcm1zIGZvciBlYWNoIGNsdXN0ZXIgaW4gYSBnZW5lLiBJdCBvdXRwdXRzIGEgZGF0YSBmcmFtZSwgbGlzdGluZyBlYWNoIHRyYW5zY3JpcHQgbWFya2VyIGluIGEgc2VwYXJhdGUgcm93LgoKYGBge3IgaWRlfQpjbHVzdGVycyA9IGxldmVscyhzZXVyYXRfb2JqQGFjdGl2ZS5pZGVudCkKc3dpdGNoX21hcmtlcnMgPSBpc29zd2l0Y2g6OklTT19TV0lUQ0hfQUxMKHNldXJhdF9vYmosIGNsdXN0ZXJzLCBhc3NheSA9ICJJU08iLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pbi5wY3QgPSAwLCBsb2dmYy50aHJlc2hvbGQgPSAwLjQwKQpgYGAKCiMjIyMgMi4zLjEgU2F2ZSBESUUgbGlzdAoKV2Ugc2F2ZSB0aGUgbGlzdCBvZiBERSBpc29mb3JtcyA6CgpgYGB7ciBzYXZlX3N3aXRjaF9tYXJrZXJzfQpzYXZlUkRTKHN3aXRjaF9tYXJrZXJzLCBmaWxlID0gcGFzdGUwKG91dF9kaXIsICIuL2RhdGFzZXRzLyIsICJzd2l0Y2hfbWFya2Vyc19jb21iaW5lZF9hbGwucmRzIikpCmBgYAoKIyMjIDIuNCBESUUgdmlzdWFsaXNhdGlvbgoKV2UgbG9hZCB0aGUgREUgaXNvZm9ybXMgdGFibGUgOgoKYGBge3IgbG9hZF9zd2l0Y2hfbWFya2Vyc30Kc3dpdGNoX21hcmtlcnMgPSByZWFkUkRTKHBhc3RlMChvdXRfZGlyLCAiL2RhdGFzZXRzL3N3aXRjaF9tYXJrZXJzX2NvbWJpbmVkX2FsbC5yZHMiKSkKYGBgCgojIyMjIDIuNC4xIEV4cGxvcmUgdGhlIHN3aXRjaCBtYXJrZXJzIHRhYmxlCgpJbiB0aGlzIHNlY3Rpb24sIHdlIGV4cGxvcmUgdGhlIGBzd2l0Y2hfbWFya2VyYCBkYXRhZnJhbWUuCgpMaXN0IG9mIGlzb2Zvcm0gc3dpdGNoczoKCmBgYHtyIGdlbmVfc3dpdGNoX3RhYmxlLCBmaWcuaGVpZ2h0ID0gOCwgZmlnLndpZHRoID0gMTV9Cmlzb3N3aXRjaDo6Z2VuZV9zd2l0Y2hfdGFibGUoc3dpdGNoX21hcmtlcnMpCmBgYAoKQ2FuIHlvdSBpZGVudGlmeSB0aGUgZ2VuZXMgYW5kIGNlbGwgdHlwZXMgdGhhdCBzaG93IHRoZSBtb3N0IHNpZ25pZmljYW50IGNoYW5nZXMgaW4gaXNvZm9ybSBleHByZXNzaW9uPwoKVG8gZmFjaWxpdGF0ZSB0aGUgZ3JhcGhpY2FsIGludGVycHJldGF0aW9uIG9mIHRoZSByZXN1bHRzOgoKICAtIGBwbG90X21hcmtlcl9tYXRyaXgoKWAgcHJvZHVjZXMgYSBoZWF0bWFwIG9mIG51bWJlciBvZiB1bmlxdWUgZ2VuZXMgcGVyIGNvbnRyYXN0IGJldHdlZW4gY2x1c3RlcnMKICAtIGBwbG90X21hcmtlcl9zY29yZSgpYCBwcm9kdWNlcyBhIHZvbGNhbm8gcGxvdCBzaG93aW5nIHAtdmFsdWVzIGFuZCBhdmVyYWdlIGxvZ0ZDIGZvciBlYWNoIGdlbmUgd2l0aCBhbiBpc29mb3JtIHN3aXRjaAoKV2UgdmlzdWFsaXplIHRoZSBudW1iZXIgb2YgREUgaXNvZm9ybXMgYmV0d2VlbiBlYWNoIGNsdXN0ZXIgOgoKYGBge3IgcGxvdF9tYXJrZXJfbWF0cml4LCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD0xMn0KcGwxID0gaXNvc3dpdGNoOjpwbG90X21hcmtlcl9tYXRyaXgoc2V1cmF0X29iaiwgc3dpdGNoX21hcmtlcnMpCnBsMiA9IGlzb3N3aXRjaDo6cGxvdF9tYXJrZXJfc2NvcmUoc2V1cmF0X29iaiwgc3dpdGNoX21hcmtlcnMpCnBsMSB8IHBsMgpgYGAKCkluZGl2aWR1YWwgdm9sY2FubyBwbG90cyBmb3IgZWFjaCBjbHVzdGVyLgogCmBgYHtyIGluZGl2aWR1YWxfbWFya2VyX3Njb3JlLCBmaWcuaGVpZ2h0PTEyLCBmaWcud2lkdGg9MTQsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHBhZ2VkLnByaW50PUZBTFNFfQppc29zd2l0Y2g6OnBsb3RfbWFya2VyX3Njb3JlKHNldXJhdF9vYmosIHN3aXRjaF9tYXJrZXJzLCBmYWNldD1UUlVFLCBuY29sPTMpCmBgYAoKIyMjIyAyLjQuMiAgVmlzdWFsaXphdGlvbiBvZiBzcGVjaWZpYyBnZW5lcwoKSW4gdGhpcyBzZWN0aW9uLCB3ZSB2aXN1YWxpemUgaXNvZm9ybXMgZXhwcmVzc2lvbiBsZXZlbCBmb3Igc3BlY2lmaWMgZ2VuZXMuIAoKRmlyc3QsIHdlIHNldCB0aGUgYXNzYXkgdG8gIklTTyIgYmVjYXVzZSBhbGwgZXhwcmVzc2lvbiBsZXZlbHMgYXJlIHN0b3JlZCB0aGVyZS4KCmBgYHtyIHNldF9kZWZhdWx0X2Fzc2F5fQpTZXVyYXQ6OkRlZmF1bHRBc3NheShzZXVyYXRfb2JqKSA9ICJJU08iCmBgYAoKTGV0J3MgdGFrZSB0aGUgZXhhbXBsZSBvZiBDbGF0aHJpbiBsaWdodCBjaGFpbiBBIChDbHRhKSBnZW5lIGlzb2Zvcm0gZXhwcmVzc2lvbiBzd2l0Y2ggZHVyaW5nIG5ldXJvbmFsIG1hdHVyYXRpb246CgpgYGB7ciBzZWVfY2RrbjJjLCBmaWcud2lkdGggPSAxMiwgZmlnLmhlaWdodCA9IDZ9CnNlZV9pc29mb3Jtc19leHByZXNzaW9uKHNldXJhdF9vYmosICJDbHRhIiwgYygwLDQpKSAlPiUKICBwYXRjaHdvcms6OndyYXBfcGxvdHMoLiwgbmNvbCA9IDMpCmBgYAoKYGBge3IgZmVhdHVyZXBsb3QsIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTE2fQpTZXVyYXQ6OkZlYXR1cmVQbG90KHNldXJhdF9vYmosIHJlZHVjdGlvbiA9IG5hbWUyRCwgZmVhdHVyZXMgPSBjKCJDbHRhLi5FTlNNVVNUMDAwMDAxMDc4NDkiLCAiQ2x0YS4uRU5TTVVTVDAwMDAwMTcwMjQxIiksIGJsZW5kID0gVFJVRSwgb3JkZXIgPSBUUlVFKQpgYGAKCiMjIyMgMi40LjMgUmVwb3J0CgpJbiB0aGlzIHNlY3Rpb24sIHdlIGdlbmVyYXRlIGEgc21hbGwgcmVwb3J0LgoKV2UgbG9hZCBhbm5vdGF0aW9uIGZpbGVzOiAKVGhlIFtJc29zd2l0Y2ggZG9jdW1lbnRhdGlvbl0oaHR0cHM6Ly9naXRodWIuY29tL3VjYWdlbm9taXgvaXNvc3dpdGNoL2Jsb2IvbWFpbi9SRUFETUVfbWV0YWRhdGEubWQpIHByb3ZpZGVzIGEgY29tcHJlaGVuc2l2ZSBndWlkZSBvbiBob3cgdG8gYnVpbGQgeW91ciBhbm5vdGF0aW9uIG1ldGFkYXRhLgoKYGBge3IgbG9hZF9hbm5vdGF0aW9uLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD0xMn0KZ3RmX2RmIDwtIHJlYWRSRFMocGFzdGUwKGRhdGFfZGlyLCJhbm5vdGF0aW9uL2d0Zl9yZHMucmRzIikpCmdlbmVfbWV0YWRhdGEgPC0gcmVhZFJEUyhwYXN0ZTAoZGF0YV9kaXIsImFubm90YXRpb24vZ2VuZV9tZXRhZGF0YS5yZHMiKSkKdHJhbnNjcmlwdF9tZXRhZGF0YSA8LSByZWFkUkRTKHBhc3RlMChkYXRhX2RpciwiYW5ub3RhdGlvbi90cmFuc2NyaXB0X21ldGFkYXRhLnJkcyIpKQpgYGAKCkZvciBDbHRhIGdlbmU6CgpgYGB7ciBpc29zd2l0Y2hfcmVwb3J0LCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD0xMn0KZ2VuZV9zd2l0Y2hlcyA8LSBpc29zd2l0Y2g6OmNvbXB1dGVfc3dpdGNoZXMoc3dpdGNoX21hcmtlcnMsIGdlbmU9IkNsdGEiKQprbml0cjo6a2FibGUoaXNvc3dpdGNoOjpmb3JtYXRfc3dpdGNoX3RhYmxlKGdlbmVfc3dpdGNoZXMpKQppc29zd2l0Y2g6Omlzb3N3aXRjaF9yZXBvcnQoc2V1cmF0X29iaiwgIklTTyIsIGdlbmU9IkNsdGEiLCBtYXJrZXJfbGlzdD1zd2l0Y2hfbWFya2VycywgZ3RmX2RmLCB0cmFuc2NyaXB0X21ldGFkYXRhKSAKYGBgCgotIE5vdywgdHJ5IGFwcGx5aW5nIHRoZSBzYW1lIGFuYWx5c2lzIHRvIGV4cGxvcmUgaXNvZm9ybSBleHByZXNzaW9uIGZvciBvdGhlciBnZW5lcyBvZiBpbnRlcmVzdC4KCiMjIyAyLjUgU2F2ZQoKV2Ugc2F2ZSB0aGUgbGlzdCBvZiBERSBpc29mb3JtcyA6CgpgYGB7ciBzYXZlX3NvYmp9CnNhdmVSRFMoc3dpdGNoX21hcmtlcnMsIGZpbGUgPSBwYXN0ZTAob3V0X2RpciwgIi4vZGF0YXNldHMvIiwgInN3aXRjaF9tYXJrZXJzX2NvbWJpbmVkX2FsbC5yZHMiKSkKYGBgCgojIyMgMi42IFJlc3NvdXJjZXMKCi0gW1NldXJhdCB0dXRvcmlhbHNdKGh0dHBzOi8vc2F0aWphbGFiLm9yZy9zZXVyYXQvKQotIFtJc29zd2l0Y2ggUiBwYWNrYWdlXShodHRwczovL2dpdGh1Yi5jb20vdWNhZ2Vub21peC9pc29zd2l0Y2gpCi0gW1JlYWQgbW9yZSBhYm91dCBTaW5nbGUtY2VsbCBsb25nLXJlYWRdKGh0dHBzOi8vd3d3Lmlzb21pY3MuZXUvaW5kZXguaHRtbCkKCiMjIyBSIHNlc3Npb24KCmBgYHtyIHNlc3Npb25pbmZvLCBlY2hvID0gRkFMU0UsIGZvbGRfb3V0cHV0ID0gVFJVRX0Kc2Vzc2lvbkluZm8oKQpgYGAK